package migrator

import (
	"fmt"
	"go-caipu/pkg/setting"
	"sync/atomic"
	"time"

	"gorm.io/gorm"

	log2 "go-caipu/pkg/infra/log"
)

var (
	ErrMigratorIsLocked   = fmt.Errorf("migrator is locked")
	ErrMigratorIsUnlocked = fmt.Errorf("migrator is unlocked")
)

type Migrator struct {
	DBEngine     *gorm.DB
	Dialect      Dialect
	migrations   []Migration
	migrationIds map[string]struct{}
	Logger       log2.Logger
	Cfg          *setting.Cfg
	isLocked     atomic.Bool
	logMap       map[string]MigrationLog
}
type MigrationLog struct {
	Id          int64  `json:"DictCode" gorm:"type:bigint(20);comment:编号"`
	MigrationID string `json:"migration_id"`
	SQL         string `json:"sql"`
	Success     bool   `json:"success"`
	Error       string `json:"error"`
	Timestamp   time.Time
}

func (m *MigrationLog) TableName() string {
	return "sys_migration_log"
}

func NewMigrator(engine *gorm.DB, Cfg *setting.Cfg) *Migrator {
	mg := &Migrator{}
	mg.DBEngine = engine
	mg.Logger = log2.New("migrator")
	mg.migrations = make([]Migration, 0)
	mg.migrationIds = make(map[string]struct{})
	mg.Dialect = NewDialect(mg.DBEngine)
	mg.Cfg = Cfg
	return mg
}
func (mg *Migrator) MigrationsCount() int {
	return len(mg.migrations)
}

// AddMigration 添加生成
func (mg *Migrator) AddMigration(id string, m Migration) {
	if _, ok := mg.migrationIds[id]; ok {
		panic(fmt.Sprintf("migration id conflict: %s", id))
	}

	m.SetId(id)
	mg.migrations = append(mg.migrations, m)
	mg.migrationIds[id] = struct{}{}
}

// GetMigrationIDs 返回生成对象主键（判断是否包含日志
func (mg *Migrator) GetMigrationIDs(excludeNotLogged bool) []string {
	result := make([]string, 0, len(mg.migrations))
	for _, migration := range mg.migrations {
		if migration.SkipMigrationLog() && excludeNotLogged {
			continue
		}
		result = append(result, migration.Id())
	}
	return result
}

// GetMigrationLog a获取历史执行成功能的自动化对象
func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) {
	logMap := make(map[string]MigrationLog)
	logItems := make([]MigrationLog, 0)

	exists := mg.DBEngine.Migrator().HasTable(&MigrationLog{})

	if !exists {
		return logMap, nil
	}
	mg.DBEngine.Find(&logItems)
	if len(logItems) == 0 { //没有查到
		return nil, nil
	}
	for _, logItem := range logItems {
		if !logItem.Success {
			continue
		}
		logMap[logItem.MigrationID] = logItem
	}

	mg.logMap = logMap
	return logMap, nil
}
func (mg *Migrator) RemoveMigrationLogs(migrationsIDs ...string) {
	for _, id := range migrationsIDs {
		delete(mg.logMap, id)
	}
}

func (mg *Migrator) Start(isDatabaseLockingEnabled bool, lockAttemptTimeOut int) (err error) {
	return mg.run()
}
func (mg *Migrator) run() (err error) {
	mg.Logger.Info("Starting DB migrations")
	_, err = mg.GetMigrationLog()
	if err != nil {
		return err
	}
	migrationsPerformed := 0
	migrationsSkipped := 0
	start := time.Now()
	for _, m := range mg.migrations {
		_, exists := mg.logMap[m.Id()]
		if exists {
			mg.Logger.Debug("Skipping migration: Already executed", "id", m.Id())
			migrationsSkipped++
			continue
		}
		sql := m.SQL(mg.Dialect)
		record := MigrationLog{
			MigrationID: m.Id(),
			SQL:         sql,
			Timestamp:   time.Now(),
		}

		//执行任务sql
		err = mg.exec(m, mg.DBEngine)
		if err != nil { //error
			record.Error = err.Error()
			if !m.SkipMigrationLog() {
				mg.DBEngine.Create(&record)
			}
			mg.Logger.Error("Exec failed", "error", err, "sql", sql)
			return fmt.Errorf("%v: %w", fmt.Sprintf("migration failed (id = %s)", m.Id()), err)
		} else {
			record.Success = true
			if !m.SkipMigrationLog() {
				mg.DBEngine.Create(&record)
			}
			migrationsPerformed++
		}

		//err = mg.DBEngine.Transaction(func(tx *gorm.DB) error {
		//	err2 := mg.exec(m, tx)
		//	if err2 != nil {
		//		mg.Logger.Error("Exec failed", "error", err, "sql", sql)
		//		record.Error = err2.Error()
		//		if !m.SkipMigrationLog() {
		//			tx.Create(&record)
		//		}
		//		return err2
		//	}
		//	record.Success = true
		//	if !m.SkipMigrationLog() {
		//		//_, err = sess.InsertLoginLog(&record)
		//		tx.Create(&record)
		//		migrationsPerformed++
		//	}
		//	return err2
		//})
		//if err != nil {
		//	return fmt.Errorf("%v: %w", fmt.Sprintf("migration failed (id = %s)", m.DictCode()), err)
		//}
	}
	mg.Logger.Info("migrations completed", "performed", migrationsPerformed, "skipped", migrationsSkipped, "duration", time.Since(start))

	return nil
}
func (mg *Migrator) exec(m Migration, sess *gorm.DB) error {
	mg.Logger.Info("Executing migration", "id", m.Id())

	tx := sess.Begin()
	condition := m.GetCondition()
	var err error
	if condition != nil {
		sql, args := condition.SQL(mg.Dialect)
		if sql != "" {
			mg.Logger.Debug("Executing migration condition SQL", "id", m.Id(), "sql", sql, "args", args)
			result := tx.Exec(sql, args...)
			err = result.Error
		}
	}
	if codeMigration, ok := m.(CodeMigration); ok {
		mg.Logger.Debug("Executing code migration", "id", m.Id())
		err = codeMigration.Exec(sess, mg)
	} else {
		sql := m.SQL(mg.Dialect)
		mg.Logger.Debug("Executing sql migration", "id", m.Id(), "sql", sql)
		result := tx.Exec(sql)
		err = result.Error
	}
	if err != nil {
		tx.Rollback()
		mg.Logger.Error("Executing migration failed", "id", m.Id(), "error", err)
		return err
	}
	tx.Commit()
	return nil
}
